#include "basiciff.hh"

#include <stdint.h>
#include <fstream>
#include <list>
#include <stdexcept>
#ifndef NDEBUG
#include <sstream>
#endif

ChunkID::ChunkID(const char *id_) : as_int(0x20202020) {
  for(register int i = 0; i < 4 && *id_ != 0; ++i) id[i] = *id_++;
}

bool ChunkID::operator<(ChunkID other) const { return as_int < other.as_int; }
bool ChunkID::operator==(ChunkID other) const { return as_int == other.as_int; }

std::ostream &operator<<(std::ostream &out, ChunkID id) {
  for(register int i = 0; i < 4; ++i) out << id.id[i];
  return out;
}
const ChunkID chunkFORM("FORM");
const ChunkID chunkCAT("CAT ");
const ChunkID chunkLIST("LIST");
const ChunkID chunkPROP("PROP");
const ChunkID chunkFiller;
const ChunkID chunkJUNK("JUNK");


std::ostream& WriterEmpty::operator()(std::ostream& o) const {
  o.write((const char*)&ckID, sizeof(ckID));
  int32_t l = 0;
  o.write((const char*)&l, sizeof(l));
  return o;
}


std::ostream& WriterGroup::write_chunks(std::ostream& o) const {
  std::list<const WriterAbstract*>::const_iterator end(chunks.end());
  for(std::list<const WriterAbstract*>::const_iterator it = chunks.begin(); it != end; ++it) {
    (*it)->operator()(o);
  }
  return o;
}

std::ostream& WriterGroup::operator()(std::ostream& o) const {
  o << groupID;
  std::ostream::pos_type beg = o.tellp();
  o.seekp(4, std::ios::cur);
  o << subtype;
  std::ostream::pos_type end = write_chunks(o).tellp();
  int32_t l = htonl(end - beg - 4);
  o.seekp(beg);
  o.write((char *)(&l), sizeof(l));
  o.seekp(end);
  return o;
};  


std::istream& ReaderChunkHeader::operator()(std::istream &strm) {
  chunksize_t size;
  ckPos = strm.tellg();
  strm.read((char *)&ckID, sizeof(ckID));
  //TODO: check if ok
  strm.read((char *)&size, sizeof(size));
  //TODO: check if ok
  ckSize = ntohl(size);
  //TODO: check size against bounds
  return strm;
}
ChunkID ReaderChunkHeader::id() const {
  return ckID;
}
chunksize_t ReaderChunkHeader::size() const {
  return ckSize;
}
off_t ReaderChunkHeader::pos() const {
  return ckPos;
}
off_t ReaderChunkHeader::endpos() const {
  return ChunkWordAlign(ckPos + ckSize + 8);
}
void ReaderChunkHeader::begin(std::istream &strm) const {
  strm.seekg(pos());
}
void ReaderChunkHeader::end(std::istream &strm) const {
  strm.seekg(endpos());
}

std::istream& ReaderChunkSkip::operator()(std::istream &strm) {
  ReaderChunkHeader::operator()(strm);
  strm.seekg(ChunkWordAlign(size()), std::ios::cur);
  return strm;
}


std::istream& ReaderGroupHeader::operator()(std::istream &strm) {
  ReaderChunkHeader::operator()(strm);
  strm.read((char *)&ckSubtype, sizeof(ckSubtype));
  return strm;
}
ChunkID ReaderGroupHeader::subtype() const {
  return ckSubtype;
}

std::istream& ReaderSkipGroup::operator()(std::istream &strm) {
  ReaderGroupHeader::operator()(strm);
  strm.seekg(ChunkWordAlign(size()), std::ios::cur);
  return strm;
}

#include <iostream>
std::istream& ReaderGroupBase::operator()(std::istream &strm) {
  ReaderGroupHeader::operator()(strm);
  off_t end = endpos();
  while(strm.tellg() < end) {
    ReaderChunkHeader hdr;
    hdr(strm);
    if(!handle(*this, hdr, strm)) {
      //Skip
      strm.seekg(ChunkWordAlign(hdr.size()), std::ios::cur);
    }
  }
  if(strm.tellg() != end) {
#ifndef NDEBUG
    std::ostringstream out;
    out << "strm.tellg() != end (" << strm.tellg() << " != " << end << ')';
    throw std::runtime_error(out.str());
#else    
    throw std::runtime_error("strm.tellg() != end");
#endif

  }
  return strm;
}

